﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Collections.Generic;

namespace Numbers
{
    /// <summary>
    /// Holds a single value, with rails
    /// </summary>
    public class Value : INotifyPropertyChanged, Numbers.Transactions.IObjectInTransaction
    {
        private double _val = 0.0;

        private double? _min = null;
        private double? _max = null;

        /// <summary>
        /// Default initializer
        /// </summary>
        public Value()
        {
        }

        /// <summary>
        /// Get the name of this value - if it was set in the first place!
        /// </summary>
        public string Name { get; private set; }

        /// <summary>
        /// Give it a name -  for errors, etc.
        /// </summary>
        /// <param name="name"></param>
        public Value(string name)
        {
            Name = name;
            _valueRegistry[name] = this;
        }

        /// <summary>
        /// Return somethign nice and simple as a representation of this string.
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return Name + "(" + _val.ToString() + ")";
        }

        /// <summary>
        /// Get/Set the minimum value for this guy - this might
        /// trigger a change in the value! Leave null for no min.
        /// </summary>
        public double? MinValue
        {
            get { return _min; }
            set
            {
                _min = value;
                ReconstrainValue();
            }
        }

        /// <summary>
        /// Get/set the max value. Leave null for no max value. Setting might change Val.
        /// </summary>
        public double? MaxValue
        {
            get { return _max; }
            set
            {
                _max = value;
                ReconstrainValue();
            }
        }

        /// <summary>
        /// Get/Set the value. It might not get set to what you think if
        /// you've got min/max set!
        /// </summary>
        public double Val
        {
            get { return _val; }
            set
            {
                double v = ConstrainValue(value);
                ChangeValue(v);
            }
        }

        /// <summary>
        /// Make sure that the value is in bounds.
        /// </summary>
        private void ReconstrainValue()
        {
            double v = ConstrainValue(_val);
            ChangeValue(v);
        }

        /// <summary>
        /// Gets/Sets the transaction manager that this Value is part of. Can be null.
        /// </summary>
        public Transactions.Manager TransactionManager { get; set; }

        /// <summary>
        /// Does the actual work of changing a value. Allows for undo-ing the change.
        /// The change can have wide-reaching effects. We need to make sure
        /// that we are part of a transaction when this is happening.
        /// </summary>
        /// <param name="v"></param>
        private void ChangeValue(double v)
        {
            if (_val != v)
            {
                using (var j = new Transactions.Manager.Joiner(TransactionManager, this, v, _val))
                {
                    if (j.Good)
                    {
                        _val = v;
                        if (!OnProposedPropertyChange("Val"))
                        {
                            /// They couldn't deal with it - make sure this doens't go any further than it needs to.
                            if (TransactionManager == null)
                                throw new InvalidOperationException("Can't fail a proposed change if no transaction manager associated!");
                            TransactionManager.ForceFail();
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Something has changed. Warn everyone!
        /// </summary>
        /// <param name="p"></param>
        private void OnPropertyChanged(string p)
        {
            if (PropertyChanged != null && !_disableNotification)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(p));
            }
        }

        /// <summary>
        /// Make sure the new value is going to be "ok".
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private double ConstrainValue(double value)
        {
            double result = value;
            if (MaxValue.HasValue && value > MaxValue.Value)
            {
                result = MaxValue.Value;
            }
            if (MinValue.HasValue && value < MinValue.Value)
            {
                result = MinValue.Value;
            }

            return result;
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        private bool _disableNotification = false;

        /// <summary>
        /// Grab an instance of this class if you want to disable sending out notifications
        /// while you do a bunch of updates. Use the "using" clause to grab it to make sure
        /// you don't leave anything dangling behind!!
        /// </summary>
        public class DisableNotifications : IDisposable
        {
            Value _obj;
            Transactions.Manager.Joiner jnr = null;
            bool _oldValueOfDisable;
            bool _newValueIsDifferent;
            double _oldVal;

            public DisableNotifications(Value obj, double newValue)
            {
                _obj = obj;
                _oldVal = _obj.Val;

                double nv = _obj.ConstrainValue(newValue);
                _newValueIsDifferent = nv != obj._val;

                jnr = new Numbers.Transactions.Manager.Joiner(obj.TransactionManager, obj, nv, obj._val);

                _oldValueOfDisable = obj._disableNotification;
                obj._disableNotification = true;
                obj.ChangeValue(nv);
            }

            /// <summary>
            /// Called to clean up once we are done.
            /// </summary>
            public void Dispose()
            {
                /// Ok - we are about to come back online! Let everyone know!
                if (!_obj._disableNotification)
                {
                    /// TODO: Bad error - someone else has already turned things back on!
                }

                ///
                /// Ok - if we are about to turn back on warnings - then we had better
                /// send some out!
                /// 

                if (!_oldValueOfDisable)
                {
                    _obj._disableNotification = false;
                    if (_oldVal != _obj.Val)
                    {
                        _obj.OnProposedPropertyChange("Val");
                    }
                }
                jnr.Dispose();
            }
        }

        /// <summary>
        /// What to watch to see if anything is changing.
        /// </summary>
        public event Numbers.Transactions.ProposedPropertyChangeCallback ProposedPropertyChange;

        /// <summary>
        /// Advertise that we'd like to change our value and see what people thing back! Returns false if things failed!
        /// </summary>
        /// <param name="propName"></param>
        private bool OnProposedPropertyChange(string propName)
        {
            if (ProposedPropertyChange != null && !_disableNotification)
            {
                return ProposedPropertyChange(this, propName);
            }
            return true;
        }

        /// <summary>
        /// Transaction is done. Roll back or not!
        /// </summary>
        /// <param name="transactionIsGood"></param>
        /// <param name="newValue"></param>
        /// <param name="oldValue"></param>
        /// <param name="raisePropChanged"></param>
        public void TransactionOver(bool transactionIsGood, double newValue, double oldValue, bool raisePropChanged)
        {
            if (!transactionIsGood)
            {
                _val = oldValue;
            }
            if (raisePropChanged && newValue != oldValue)
            {
                OnPropertyChanged("Val");
            }
        }

        /// <summary>
        /// Keep a list of these guys internally
        /// </summary>
        static Dictionary<string, Value> _valueRegistry = new Dictionary<string, Value>();

        /// <summary>
        /// Return a named Value. Null if not existing.
        /// </summary>
        /// <param name="p"></param>
        /// <returns></returns>
        public static Value FindValue(string valueName)
        {
            Value result;
            if (!_valueRegistry.TryGetValue(valueName, out result))
            {
                return null;
            }
            return result;
        }
    }
}
